Skip to main content

Simulating transactions

Overview

It's time to simulate the real-world behavior of your card program using the Core API.

tip

For a deep dive on how transactions, payments, and authorizations work see Transaction Lifecyle and Transaction States and Types.

Goals

At the end of this guide, you should have been able to:

info

Simulating transactions is a feature only available in the sandbox environment and intended for testing your integration.

Prerequisites

Before simulating transactions, you'll need:

  1. An API Key for making calls to the Core API.
  2. Test cardholders, you can create them following this guide.
  3. Each test cardholder must be issued a card.
  4. Each card will need to have funds to draw from.

The nature of a transaction

A transaction operation is composed of four steps:

  1. Authorization
  2. Holding funds
  3. Clearing funds
  4. Settlement

Step 1 (authorization) and Step 2 (holding funds) happen synchronously one after the other. However, Step 3 (clearing funds) and Step 4 (settlement) happen at a later time–as late as at the end of the day, which allows the merchant to amend the transaction amount or even cancel it.

When simulating transactions, we're going to simulate Step 1 and Step 2 and Step 3 through the Core API.

Step 4 is performed by Apto, so is not important for us in this case.

Indicating transaction type

When simulating transactions, we're going to indicate to the Core API what type of transaction we're simulating via the type field in the POST request, and how to process it via the processing_type field.

The processing_type field indicates whether the transaction should attempt to authorize and capture funds in a single or multiple API calls and whether it can be declined or not.

info

Remember the "capture" request sent by the merchant–in this case, as you're simulating the transaction, will trigger clearing funds and settlement, which marks the transaction as complete and moves funds to the merchant account.

Checking the card balance

First, verify whether the card we want to simulate transactions on has enough funds.

Send a query to the /cards/{CARD_ID} endpoint to retrieve the card's details:

curl --location --request GET 'https://api.sbx.aptopayments.com/cards/{CARD_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}'

The response will contain fields indicating the card total balance, and how much is available for use.

{
"card": {
"id": "crd_235ec5ec0032c815",
"activated_at": "2020-11-19T19:09:31Z",
"created_at": "2020-11-19T19:09:31Z",
// ...
"spendable_today": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
},
"spendable_balance": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
},
"total_balance": {
"amount": 105.33,
"currency": "USD",
"native_amount": 105.33,
"native_currency": "USD"
}
}
}

In this case the card has a total balance of $105.33 so purchases can be made with it.

Authorization and capturing funds in a single request

The simplest transactions to simulate are those where authorization and capture are attempted with a single request.

These types of transactions have processing_type as financial_request. The following two examples show a declined transaction and an approved transaction.

Declined financial request

If a cardholder tries to spend more money than they have, the transaction request will be declined due to insufficient funds.

Submit a POST request to the /cards/{card_id}/transactions endpoint with a billing amount greater than the card balance.

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 999.34,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}'

The response should show that the transaction was declined due to insufficient funds on the card, as indicated in the decline_reason field:

{
"transaction": {
"id": "txn_724ce490de1a54e1",
"authorizations": [
{
"authorization": {
"id": "auth_4801faf4602d0705",
"issuer_data": null,
"decline_code": "decline_nsf",
"decline_reason": "INSUFFICIENT FUNDS",
"external_authorization_id": null,
"authorized": false,
"created_at": "2020-11-19T19:57:47Z"
}
}
],
"adjustments": []
// ...
}
}

You can learn more about the different decline codes in the Core API documentation.

Approved financial request

Alternatively, if a cardholder makes a transaction with an amount less than the card's total balance, the transaction should be approved:

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 8.12,
"processing_type": "financial_request",
"type": "purchase",
"description": "Test"
}'

The response will show that the transaction was approved, as reflected in the "authorized": true field and the "state": complete field.

{
"transaction": {
"id": "txn_eb5273a61f9e1354",
"authorizations": [
{
"authorization": {
"id": "auth_3f825a465342cbe3",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T20:00:05Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_d557583a99004fbc",
"created_at": "2020-11-19T20:00:05Z",
"local_amount": {
"amount": -8.12,
"currency": "USD"
},
"billing_amount": {
"amount": -8.12,
"currency": "USD"
},
"native_amount": {
"amount": -8.12,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete"
// ...
}
}

In this transaction, both the authorization and capture steps occurred within a single request.

Visit the developer portal and click on the "Transactions" tab, you should see the two transactions we simulated:

Authorization and capture as separate requests

In a multi-message authorization and capture process, the merchant requests an authorization for a purchase but keeps the option to change the amount. The final amount is sometimes different than the original authorization request.

For example, you might see a transaction like this when you deny a retailer’s request to “round up” your purchase amount to donate to a charity.

To simulate this scenario, you’ll make multiple API calls for each transaction, each time:

  • The first API call
    • represents a merchant requesting verification of the card and its available balance.
  • Subsequent API call(s)
    • represents the merchant confirmation for capturing the funds.

Declined authorization request

Similar to financial request transactions, you can simulate a declined authorization request transaction by simulating the cardholder attempting to spend more money than their total available balance.

note

Because our request will be declined, we will not send a second API call for capturing the funds.

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 999.34,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'

The response should show that the transaction was declined due to insufficient funds on the card, as indicated by the decline_reason field:

{
"transaction": {
"id": "txn_724ce490de1a54e1",
"authorizations": [
{
"authorization": {
"id": "auth_4801faf4602d0705",
"issuer_data": null,
"decline_code": "decline_nsf",
"decline_reason": "INSUFFICIENT FUNDS",
"external_authorization_id": null,
"authorized": false,
"created_at": "2020-11-19T19:57:47Z"
}
}
],
"adjustments": []
// ...
}
}

You can learn more about the different decline codes in the Core API documentation.

Approved authorization request – equal to the pending amount

Submit an initial POST request to the /cards/{card_id}/transactions endpoint with a billing amount less than the card balance:

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 10.15,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'

The response will indicate that the transaction state is pending ("state": "pending") given you have not asked to capture the funds on the cardholder account yet.

{
"transaction": {
"livemode": true,
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_a843a8e498b28766",
"created_at": "2020-11-19T21:50:16Z",
"local_amount": {
"amount": -10.15,
"currency": "USD"
},
"billing_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "pending"
// ...
}
}

The recently authorized transaction can now be captured. In this case, it is important that the request to capture the funds should bill an equal amount to the original authorized amount.

Send a PUT request to the /cards/{card_id}/transactions/{transaction_id} endpoint, supplying the transaction ID from the previous response and the matching authorized amount:

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 10.15,
"processing_type": "financial_advice"
}'

If all went well, the response will include a "state": "complete" field, indicating the transaction is now complete:

{
"transaction": {
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_a843a8e498b28766",
"created_at": "2020-11-19T21:50:16Z",
"local_amount": {
"amount": -10.15,
"currency": "USD"
},
"billing_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_amount": {
"amount": -10.15,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete",
// ...
}

Approved authorization request – less than the pending amount

In a multi-message authorization and capture process, the merchant requests an authorization for a purchase but keeps the option to change the amount.

The final amount is sometimes less than the original authorization request. For example, you might see a transaction like this when you buy fuel from a gas station.

Submit a POST request to the /cards/{card_id}/transactions endpoint with a billing amount less than the card balance. When the request is approved, note the transaction ID in the response. You’ll need it to update the transaction with the final purchase amount. The approved request will have a state of pending.

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 12.34,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'
{
"transaction": {
"id": "txn_93879ffb2b5c14a8",
"authorizations": [
{
"authorization": {
"id": "auth_3ab56a72a41a30e1",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T19:44:23Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_66556b71ce1d908c",
"created_at": "2020-11-19T19:44:23Z",
"local_amount": {
"amount": -12.34,
"currency": "USD"
},
"billing_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "pending",
// ...
}

Notice the state is pending. You can now complete the previously authorized transaction, informing the issuer of the final amount. In this case, the cleared transaction is less than the original authorized amount.

When making the capture request, you must specify a smaller billing amount ($12) than what was stated in the initial authorization request ($12.34):

curl --location --request PUT 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 12.00,
"processing_type": "financial_advice"
}'
{
"transaction": {
"id": "txn_93879ffb2b5c14a8",
"authorizations": [
{
"authorization": {
"id": "auth_3ab56a72a41a30e1",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T19:44:23Z"
}
}
],
"adjustments": [
{
"adjustment": {
"id": "adj_5e1cfe1020273b50",
"created_at": "2020-11-19T19:53:47Z",
"local_amount": {
"amount": 0.34,
"currency": "USD"
},
"billing_amount": {
"amount": 0.34,
"currency": "USD"
},
"native_amount": {
"amount": 0.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": 0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "refund"
}
},
{
"adjustment": {
"id": "adj_66556b71ce1d908c",
"created_at": "2020-11-19T19:44:23Z",
"local_amount": {
"amount": -12.34,
"currency": "USD"
},
"billing_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_amount": {
"amount": -12.34,
"currency": "USD"
},
"native_fee_amount": {
"amount": -0.0,
"currency": "USD"
},
"funding_source_transaction_id": null,
"funding_source_name": "",
"exchange_rate": 1.0,
"type": "capture"
}
}
],
// ...
"state": "complete",
// ...
}

Notice the response includes "state": "complete". This completes the transaction.

Approved authorization request – greater than the pending amount

The final amount for a transaction can be sometimes greater than the original authorization request amount. For example, you might see a transaction like this when you buy a meal at a restaurant and leave a tip.

To simulate this scenario, let's create the same authorization request with a billing amount of $21.22:

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 21.22,
"processing_type": "authorization_request",
"type": "purchase",
"description": "Test"
}'

When making the capture request, this time you will specify a greater billing amount ($25) than what was stated in the initial authorization request ($21.22):

curl --location --request PUT 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions/{TR_ID}' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 25.0,
"processing_type": "financial_advice"
}'

If all went well, the response will include a "state": "complete" field, indicating the transaction is now complete:

{
"transaction": {
"id": "txn_d9ab131480f8ff34",
"authorizations": [
{
"authorization": {
"id": "auth_5edc22f5e5241314",
"issuer_data": null,
"decline_code": null,
"decline_reason": null,
"external_authorization_id": null,
"authorized": true,
"created_at": "2020-11-19T21:50:16Z"
}
}
],
// ...
"state": "complete",
// ...
}

Visit the developer portal and click on the "Transactions" tab, you should all the transactions we simulated so far:

Refund

Refunds can come in various forms depending on how the merchant processes the refund.

This example simulates a very common merchant refund where the refund is a financial advice, also known as a force post, which Apto cannot decline.

Submit a POST request to the /cards/{card_id}/transactions endpoint with a billing amount equivalent to the desired amount to be refunded and specifying the transaction type as refund:

curl --location --request POST 'https://api.sbx.aptopayments.com/cards/{CARD_ID}/transactions' \
--header 'Authorization: Basic {CORE_API_KEY}' \
--header 'Content-Type: application/json' \
--data-raw '{
"billing_amount": 105.33,
"processing_type": "financial_advice",
"type": "return",
"description": "Test"
}'

If all went well, the response will contain a "state": "complete" field indicating you have successfully refunded $105.33 back to the card.

{
"transaction": {
"id": "txn_f8332b818575e541",
"authorizations": [],
// ...
"state": "complete",
"type": "REFUND",
"billing_amount": {
"currency": "USD",
"amount": 105.33
}
}
}

Glossary

Transaction processing types

authorization_request

Creates a temporary hold on a card. The amount adjusts the cardholder's balance, but does not affect settlement. It is just securing the funds on the balance in case the merchant later sends a settlement message.

An Authorization Request can be declined by the cardholder's balance. In production, this is a synchronous API call that is time sensitive. Authorization requests are usually automatically released within three working days, and they should never exist beyond one month.

financial_request

Similar to Authorization Request actions, these do not need to adjust the amount or need to create a temporary hold. This means that the card networks will claim these funds during the following day’s settlement.

financial_advice

Otherwise known as force posts, these create a transaction without a previous hold having been made on the account. These are rare scenarios, and are not time-sensitive transactions. If they induce a negative balance situation, they are usually resolved with the cardholder or by disputing the transaction with the networks. Apto cannot decline these messages.